/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University. All rights reserved.
 * 
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license; 2) All redistributions in binary form
 * must reproduce the above copyright notice, this list of conditions and the
 * disclaimer listed in this license in the documentation and/or other materials
 * provided with the distribution; 3) Any documentation included with all
 * redistributions must include the following acknowledgement:
 * 
 * "This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 * 
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 * 
 * 4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University. For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202. 5) Products
 * derived from this software may not be called NaradaBrokering, nor may Indiana
 * University or Community Grids Lab or NaradaBrokering appear in their name,
 * without prior written permission of ARTI.
 * 
 * 
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of any
 * other entity. Indiana University disclaims any liability to any recipient for
 * claims brought by any other entity based on infringement of intellectual
 * property rights or otherwise.
 * 
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 * NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 * GENERATED USING SOFTWARE.
 */
package cgl.narada.protocol;

import java.util.Hashtable;

/**
 * This class is responsible for the creation of gateway between two units at
 * different levels. This process of setting up connection between two units
 * must satisfy the constraints imposed by the setup rules.
 * <li> None of the server nodes involved in the connection, must have reached
 * their connection threshold for concurrent connections at the given level.
 * <li>The nodes involved in the gateway setup process, should both have valid
 * logical addresses assigned by the system.
 * <li> If the node seeks to be a level <b>k</b> gateway, their logical address
 * should differ at level <b>k</b>. Higher level addresses should be the same.
 * Thus if we can create a level-1 connection between server nodes <b>A.b.12.x</b>
 * and <b>A.b.11.y</b>. A similar connection request between <b>A.b.12.x</b>
 * and <b>B.c.11.y</b> is going to fail.
 * 
 * @author Shrideep Pallickara $Revision$ $Date$
 * 
 */

public class GatewayPropagationProtocol implements ProtocolDebugFlags {

    /**
     * The first element represents the # of open connections allowed as a
     * level-1 gateway, and so on.
     */
    private short[] connectionVector;

    /** Maintains information about revelant gatekeepers within the system */
    private GatewayInfo gatewayInfo;

    /** The protocol handler */
    private ProtocolHandler protocolHandler;

    /** The systemLevel */
    private int systemLevel;

    /** The node's address */
    private NodeAddress thisNodeAddress;

    /** This destination */
    private Destinations thisDestination;

    /** The gatewayRequest Table */
    Hashtable connectionRequestTable;


    /**
     * The GatewayPropagation Protocol - Testing purposes
     * 
     * @param connectionVector
     *            The connection vector for the node in question, which
     *            maintains information regarding the number of allowed
     *            conncurrent connections at different levels.
     * @param gatewayInfo
     *            The connectivity graph handle
     */
    public GatewayPropagationProtocol(short[] connectionVector,
            GatewayInfo gatewayInfo) {
        this.connectionVector = connectionVector;
        this.gatewayInfo = gatewayInfo;
        systemLevel = gatewayInfo.getSystemLevel();
        connectionRequestTable = new Hashtable();
    }


    /**
     * The GatewayPropagation Protocol - Constructor
     * 
     * @param connectionVector
     *            The connection vector for the node in question, which
     *            maintains information regarding the number of allowed
     *            conncurrent connections at different levels.
     * @param gatewayInfo
     *            The connectivity graph handle
     * @param protocolHandler
     *            A reference to the protocol Handler. To be able to setup
     *            gateways, the node in question should have an assigned
     *            address.
     */
    public GatewayPropagationProtocol(short[] connectionVector,
            GatewayInfo gatewayInfo, ProtocolHandler protocolHandler) {
        this.connectionVector = connectionVector;
        this.gatewayInfo = gatewayInfo;
        this.protocolHandler = protocolHandler;
        thisNodeAddress = protocolHandler.getAssignedAddress();
        thisDestination = protocolHandler.getNodeDestination();
        systemLevel = gatewayInfo.getSystemLevel();
        connectionRequestTable = new Hashtable();
        if (GatewayPropagation_Debug)
            System.out.println("GatewayPropagation: Initialization complete ");
    }


    /**
     * This method is used for establishing a connection between two units in
     * the system. This request can succed only if the following hold true.
     * <li> This node has been assigned a logical address by the system.
     * <li> If the node seeks to be a level <b>k</b> gateway, their logical
     * address should differ at level <b>k</b>. Higher level addresses should
     * be the same. Thus if we can create a level-1 connection between server
     * nodes <b>A.b.12.x</b> and <b>A.b.11.y</b>. A similar connection request
     * between <b>A.b.12.x</b> and <b>B.c.11.y</b> is going to fail.
     * 
     */
    public void handleGatewayCreationRequest(String node,
                                             byte[] gatewayCreationRequest) {
        int offset = 0;
        offset++; /*
                     * Skipping the GPP_SETUP_REQUEST header byte, already read
                     * by the protocol Handler
                     */
        /** Retrieving the gateway level */
        int gatewayLevel = gatewayCreationRequest[offset++];

        /** Retrieving the protocol ID bytes */
        byte[] idBytes = new byte[10];
        System.arraycopy(gatewayCreationRequest, offset, idBytes, 0, 10);
        ProtocolID requestId = new ProtocolID(idBytes);
        offset += 10;

        /** Unmarshall the requesting Node address */
        byte[] requestingNodeAddressBytes = new byte[4 * (systemLevel + 1)];
        System.arraycopy(gatewayCreationRequest, offset,
                         requestingNodeAddressBytes, 0, 4 * (systemLevel + 1));

        Destinations requestingDestination =
            new Destinations(requestingNodeAddressBytes);
        NodeAddress requestingNodeAddress =
            new NodeAddress(requestingNodeAddressBytes);
        byte[] connectionSetupResponse;
        System.out.println("\n"
            + "Gateway SETUP Request received for a level (" + gatewayLevel
            + ") gateway from Node " + requestingNodeAddress + "\n"
            + "Address of this server node is " + thisNodeAddress);

        /**
         * Performs a check to see if the creation of a gateway between these
         * units can cause a consistency problem in our system
         */
        for (int i = systemLevel; i > gatewayLevel; i--) {
            if (requestingDestination.getDestinationsAtLevel(i) != thisDestination
                .getDestinationsAtLevel(i)) {
                /**
                 * This implies that the condition for validity was not
                 * satisfied. We now need to proceed with the creation of an
                 * error request, and send the response to the requesting node
                 */
                System.out
                    .println("This was not a vaild Gateway creation request ");
                connectionSetupResponse =
                    createConnectionSetupResponse(false, gatewayLevel,
                                                  requestId, thisNodeAddress);
                protocolHandler.sendTo(node, connectionSetupResponse);
                return;
            }
        }
        /**
         * This means that it is safe to create the connection between the two
         * units
         */

        int[] addressIntsOne = requestingNodeAddress.getAddressInInts();
        int[] addressConstructOne =
            { addressIntsOne[systemLevel - gatewayLevel] };
        NodeAddress nodeOne = new NodeAddress(addressConstructOne);
        int levelOne = gatewayLevel;

        int[] addressIntsTwo = thisNodeAddress.getAddressInInts();
        int[] addressConstructTwo = { addressIntsTwo[systemLevel] };
        NodeAddress nodeTwo = new NodeAddress(addressConstructTwo);
        int levelTwo = 0;
        // //////////////////////////////////////////////////////////////
        /** ***************** FIX Complete **************************** */

        Connection createdConnection =
            new Connection(thisNodeAddress, nodeOne, levelOne, nodeTwo,
                           levelTwo, gatewayLevel, requestId);
        Gateway hop =
            new Gateway(gatewayInfo.getVertexNode(), nodeOne, (short) levelOne);
        connectionSetupResponse =
            createConnectionSetupResponse(true, gatewayLevel, requestId,
                                          thisNodeAddress);

        protocolHandler.mapLinkToBrokerNode(node, hop, requestId);
        protocolHandler.sendToNode(hop, connectionSetupResponse);
        protocolHandler.propagateConnectionInformation(createdConnection, hop);
    }


    private byte[] createConnectionSetupResponse(boolean setupSuccess,
                                                 int gatewayLevel,
                                                 ProtocolID requestId,
                                                 NodeAddress secondAddress) {
        byte[] connectionSetupResponse =
            new byte[1 /* GPP_SETUP_RESPONSE */+ 1 /* SUCESS/FAILURE 1/0 */+ 1 /* gatewayLevel */
                + 10/** Protocol/Connection ID */
                + 4 * (systemLevel + 1) /* Node Addr */];
        int offset = 0;
        connectionSetupResponse[offset++] = Protocol.GPP_SETUP_RESPONSE;
        if (setupSuccess)
            connectionSetupResponse[offset++] = 1; /* indicates a success */
        else
            connectionSetupResponse[offset++] = 0; /* indicates a failure */

        connectionSetupResponse[offset++] = (byte) gatewayLevel;
        System.arraycopy(requestId.getBytes(), 0, connectionSetupResponse,
                         offset, 10);
        offset += 10;
        System
            .arraycopy(secondAddress.getAddressInBytes(), 0,
                       connectionSetupResponse, offset, 4 * (systemLevel + 1));
        offset += (4 * (systemLevel + 1));
        return connectionSetupResponse;
    }


    /**
     * This method is responsible for processing the connection set up response.
     * If the connection setup was successful, this method is also responsible
     * for the dissemination of connection information throughout the relevant
     * parts of the system.
     * 
     * @param node
     *            The node issuing the response, this node is then mapped to a
     *            logical hop of this server node.
     * @param gatewaySetupResponse
     *            A marshalled representation of the gateway setup response.
     */
    public void handleConnectionSetupResponse(String node,
                                              byte[] gatewaySetupResponse) {
        int offset = 0;
        offset++;
        /** Skipping the GPP_SETUP_RESPONSE */
        boolean success = true;
        if (gatewaySetupResponse[offset++] != 1) {
            success = false;
        }
        int gatewayLevel = gatewaySetupResponse[offset++];
        /** Retrieving the protocol ID bytes */
        byte[] idBytes = new byte[10];
        System.arraycopy(gatewaySetupResponse, offset, idBytes, 0, 10);
        ProtocolID requestId = new ProtocolID(idBytes);
        offset += 10;

        /** Unmarshall the requesting Node address */
        byte[] respondingNodeAddressBytes = new byte[4 * (systemLevel + 1)];
        System.arraycopy(gatewaySetupResponse, offset,
                         respondingNodeAddressBytes, 0, 4 * (systemLevel + 1));
        Destinations respondingDestination =
            new Destinations(respondingNodeAddressBytes);
        NodeAddress respondingNodeAddress =
            new NodeAddress(respondingNodeAddressBytes);

        if (!success) {
            System.out.println("Connection Setup To create a level ("
                + gatewayLevel + ") to Node with address "
                + respondingNodeAddress + " has failed ");
            return;
        }

        
        int[] addressIntsOne = thisNodeAddress.getAddressInInts();
        int[] addressConstructOne = { addressIntsOne[systemLevel] };
        NodeAddress nodeOne = new NodeAddress(addressConstructOne);
        int levelOne = 0;

        int[] addressIntsTwo = respondingNodeAddress.getAddressInInts();
        int[] addressConstructTwo =
            { addressIntsTwo[systemLevel - gatewayLevel] };
        NodeAddress nodeTwo = new NodeAddress(addressConstructTwo);
        int levelTwo = gatewayLevel;
        // //////////////////////////////////////////////////////////////
        /** *****************FIX Complete****************** */

        Connection createdConnection =
            new Connection(thisNodeAddress, nodeOne, 0, nodeTwo, levelTwo,
                           gatewayLevel, requestId);

        Gateway hop =
            new Gateway(gatewayInfo.getVertexNode(), nodeTwo, (short) levelTwo);

        protocolHandler.mapLinkToBrokerNode(node, hop, requestId);
        System.out.println("The gateway set response was received for the "
            + " Requested Gateway Level = " + gatewayLevel
            + " The System Level = " + systemLevel
            + " Connection Established to " + respondingNodeAddress
            + " NodeOne " + nodeOne + " Nodetwo " + nodeTwo);

        protocolHandler.propagateConnectionInformation(createdConnection, hop);
    }


    /**
     * This method is responsible for the creation of a gateway setup request.
     * Each request needs to have a unique id associated with it, upon success
     * this ID would be used as the connection ID of the newly created
     * connection.
     * 
     * @param node
     *            The node to which this request needs to be issued to, this
     *            node has not yet been configured as a <i>hop</i> by the
     *            system.
     * @param gatewayLevel
     *            The gateway level that this node seeks to be.
     * 
     * @return The marshalled connection setup request.
     */
    public byte[] constructGatewaySetupRequest(String node, int gatewayLevel) {
        byte[] gatewaySetupRequest =
            new byte[1 /* GPP_SETUP_REQUEST */+ 1 /* Gateway Level */+ 10 /*
                                                                         * Protocol
                                                                         * ID
                                                                         */
                + 4 * (systemLevel + 1) /* Node Address */];
        int offset = 0;
        gatewaySetupRequest[offset++] = Protocol.GPP_SETUP_REQUEST;
        gatewaySetupRequest[offset++] = (byte) gatewayLevel;
        ProtocolID requestId = protocolHandler.getUniqueProtocolID();
        connectionRequestTable.put(requestId, node);

        byte[] idBytes = requestId.getBytes();
        System.arraycopy(idBytes, 0, gatewaySetupRequest, offset, 10);
        offset += 10;

        System.arraycopy(thisNodeAddress.getAddressInBytes(), 0,
                         gatewaySetupRequest, offset, 4 * (systemLevel + 1));
        offset += (4 * (systemLevel + 1));
        return gatewaySetupRequest;
    }


    /** The test function for the gateway propagation protocol. */
    public static void main(String[] args) {
        System.out.println("GatewayPropagationProtocol");
    }
}
